package testing;
import processing.core.*;
import processing.xml.*;

import java.applet.*;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.MouseEvent;
import java.awt.event.KeyEvent;
import java.awt.event.FocusEvent;
import java.awt.Image;
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import java.util.zip.*;
import java.util.regex.*;

public class VisualStatistics extends PApplet {

	// Code from Visualizing Data, First Edition, Copyright 2008 Ben Fry.
	// Based on the GraphLayout example by Sun Microsystems.

	int nodeCount;
	Node[] nodes = new Node[100];
	HashMap nodeTable = new HashMap();

	int edgeCount;
	Edge[] edges = new Edge[500];

	static final int nodeColor = 0xffF0C070;
	static final int selectColor = 0xffFF3030;
	static final int fixedColor = 0xffFF8080;
	static final int edgeColor = 255;
 
	PFont font;

	public void setup() {
		size(600, 600);

		loadData();
		font = createFont("SansSerif", 10);
		writeData();
	}

	public void writeData() {
		PrintWriter writer = createWriter("huckfinn.dot");
		writer.println("digraph output {");
		for (int i = 0; i < edgeCount; i++) {
			String from = "\"" + edges[i].from.label + "\"";
			String to = "\"" + edges[i].to.label + "\"";
			writer.println(TAB + from + " -> " + to + ";");
		}
		writer.println("}");
		writer.flush();
		writer.close();
	}

	public void loadData() {
		String[] lines = loadStrings("huckfinn.txt");

		// Make the text into a single String object
		String line = join(lines, " ");

		// Replace -- with an actual em dash
		line = line.replaceAll("--", "\u2014");

		// Split into phrases using any of the provided tokens
		String[] phrases = splitTokens(line, ".,;:?!\u2014\"");
		// println(phrases);

		for (int i = 0; i < phrases.length; i++) {
			// Make this phrase lowercase
			String phrase = phrases[i].toLowerCase();
			// Split each phrase into individual words at one or more spaces
			String[] words = splitTokens(phrase, " ");
			for (int w = 0; w < words.length - 1; w++) {
				addEdge(words[w], words[w + 1]);
			}
		}
	}

	public void addEdge(String fromLabel, String toLabel) {
		// Filter out unnecessary words
		if (ignoreWord(fromLabel) || ignoreWord(toLabel))
			return;

		Node from = findNode(fromLabel);
		Node to = findNode(toLabel);
		from.increment();
		to.increment();

		for (int i = 0; i < edgeCount; i++) {
			if (edges[i].from == from && edges[i].to == to) {
				edges[i].increment();
				return;
			}
		}

		Edge e = new Edge(from, to);
		e.increment();
		if (edgeCount == edges.length) {
			edges = (Edge[]) expand(edges);
		}
		edges[edgeCount++] = e;
	}

	String[] ignore = { "a", "of", "the", "i", "it", "you", "and", "to" };

	public boolean ignoreWord(String what) {
		for (int i = 0; i < ignore.length; i++) {
			if (what.equals(ignore[i])) {
				return true;
			}
		}
		return false;
	}

	public Node findNode(String label) {
		label = label.toLowerCase();
		Node n = (Node) nodeTable.get(label);
		if (n == null) {
			return addNode(label);
		}
		return n;
	}

	public Node addNode(String label) {
		Node n = new Node(label);
		if (nodeCount == nodes.length) {
			nodes = (Node[]) expand(nodes);
		}
		nodeTable.put(label, n);
		nodes[nodeCount++] = n;
		return n;
	}

	public void draw() {
		if (record) {
			beginRecord(PDF, "output.pdf");
		}

		background(0);
		textFont(font);
		smooth();

		for (int i = 0; i < edgeCount; i++) {
			edges[i].relax();
		}
		for (int i = 0; i < nodeCount; i++) {
			nodes[i].relax();
		}
		for (int i = 0; i < nodeCount; i++) {
			nodes[i].update();
		}
		for (int i = 0; i < edgeCount; i++) {
			edges[i].draw();
		}
		for (int i = 0; i < nodeCount; i++) {
			nodes[i].draw();
		}

		if (record) {
			endRecord();
			record = false;
		}
	}

	boolean record;

	public void keyPressed() {
		if (key == 'r') {
			record = true;
		}
	}

	Node selection;

	public void mousePressed() {
		// Ignore anything greater than this distance
		float closest = 20;
		for (int i = 0; i < nodeCount; i++) {
			Node n = nodes[i];
			float d = dist(mouseX, mouseY, n.x, n.y);
			if (d < closest) {
				selection = n;
				closest = d;
			}
		}
		if (selection != null) {
			if (mouseButton == LEFT) {
				selection.fixed = true;
			} else if (mouseButton == RIGHT) {
				selection.fixed = false;
			}
		}
	}

	public void mouseDragged() {
		if (selection != null) {
			selection.x = mouseX;
			selection.y = mouseY;
		}
	}

	public void mouseReleased() {
		selection = null;
	}

	// Code from Visualizing Data, First Edition, Copyright 2008 Ben Fry.
	// Based on the GraphLayout example by Sun Microsystems.

	class Edge {
		Node from;
		Node to;
		float len;
		int count;

		Edge(Node from, Node to) {
			this.from = from;
			this.to = to;
			this.len = 10;
		}

		public void increment() {
			count++;
		}

		public void relax() {
			float vx = to.x - from.x;
			float vy = to.y - from.y;
			float d = mag(vx, vy);
			if (d > 0) {
				float f = (len - d) / (d * 3);
				float dx = f * vx;
				float dy = f * vy;
				to.dx += dx;
				to.dy += dy;
				from.dx -= dx;
				from.dy -= dy;
			}
		}

		public void draw() {
			stroke(edgeColor);
			// strokeWeight(0.35f);
			line(from.x, from.y, to.x, to.y);
		}
	}

	// Code from Visualizing Data, First Edition, Copyright 2008 Ben Fry.
	// Based on the GraphLayout example by Sun Microsystems.

	class Node {
		float x, y;
		float dx, dy;
		boolean fixed;
		String label;
		int count;

		Node(String label) {
			this.label = label;
			x = random(width);
			y = random(height);
		}

		public void increment() {
			count++;
		}

		public void relax() {
			float ddx = 0;
			float ddy = 0;

			for (int j = 0; j < nodeCount; j++) {
				Node n = nodes[j];
				if (n != this) {
					float vx = x - n.x;
					float vy = y - n.y;
					float lensq = vx * vx + vy * vy;
					if (lensq == 0) {
						ddx += random(1);
						ddy += random(1);
					} else if (lensq < 100 * 100) {
						ddx += vx / lensq;
						ddy += vy / lensq;
					}
				}
			}
			float dlen = mag(ddx, ddy) / 2;
			if (dlen > 0) {
				dx += ddx / dlen;
				dy += ddy / dlen;
			}
		}

		public void update() {
			if (!fixed) {
				x += constrain(dx, -5, 5);
				y += constrain(dy, -5, 5);

				x = constrain(x, 0, width);
				y = constrain(y, 0, height);
			}
			dx /= 2;
			dy /= 2;
		}

		public void draw() {
			fill(nodeColor);
			stroke(0);
			strokeWeight(0.5f);

			ellipse(x, y, count * 10, count * 10);
			// translate(x, y, 150);
			// sphere(1);

			float w = textWidth(label);

			if (count > w + 2) {
				fill(0);
				textAlign(CENTER, CENTER);
				text(label, x, y);
			}
		}
	}

}
